package cn.feizhu.aimarket.controller;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.feizhu.aimarket.service.AppService;
import cn.feizhu.aimarket.service.BookService;
import cn.feizhu.aimarket.tools.MinioUtils;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import cn.feizhu.aimarket.ratelimit.annotation.RateLimit;
import cn.feizhu.aimarket.ratelimit.enums.RateLimitType;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
@RequestMapping("/download")
public class DownLoadController {

    @Resource
    private MinioUtils minioUtils;

    @Resource
    private BookService bookService;

    /**
     * 直接下载文件
     * @param filePath 文件相对路径
     * @param bucketName 桶名（可选，不传则使用默认桶）
     * @param response HTTP响应
     */
    @SaCheckLogin
    @RateLimit(limitType = RateLimitType.USER, rate = 2, rateInterval = 200000, timeUnit = TimeUnit.SECONDS, message = "下载次数已达今日上限", vipRateOverride = 10)
    @GetMapping("/file")
    public void downloadFile(@RequestParam("filePath") String filePath,
                           @RequestParam(value = "bucketName", required = false) String bucketName,
                           HttpServletRequest request,
                           HttpServletResponse response) {

        log.info("开始下载文件 - filePath: {}, bucketName: {}", filePath, bucketName);

        try {
            // 使用默认桶名如果未指定
            String targetBucket = StrUtil.isBlank(bucketName) ? minioUtils.getDefaultBucketName() : bucketName;

            // 检查文件是否存在
            if (!minioUtils.isObjectExist(targetBucket, filePath)) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("文件不存在");
                return;
            }

            // 获取文件名
            String fileName = getFileNameFromPath(filePath);

            // 设置响应头
            setDownloadHeaders(response, fileName, request);

            // 获取文件流并写入响应
            try (InputStream inputStream = minioUtils.getObject(targetBucket, filePath);
                 OutputStream outputStream = response.getOutputStream()) {

                IoUtil.copy(inputStream, outputStream);
                outputStream.flush();

                log.info("文件下载成功 - fileName: {}", fileName);
            }
            bookService.updateBookDownloadCount(filePath);

        } catch (Exception e) {
            log.error("文件下载失败 - filePath: {}, error: {}", filePath, e.getMessage(), e);

            // 检查是否是客户端连接中断异常
            if (isClientAbortException(e)) {
                log.warn("客户端中断下载连接 - filePath: {}", filePath);
                return; // 客户端已断开，不需要写入响应
            }

            // 检查响应是否已提交
            if (!response.isCommitted()) {
                try {
                    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    response.getWriter().write("文件下载失败: " + e.getMessage());
                } catch (IOException ioException) {
                    log.error("写入错误响应失败", ioException);
                }
            } else {
                log.warn("响应已提交，无法写入错误信息 - filePath: {}", filePath);
            }
        }
    }

    /**
     * 获取文件预览链接
     * @param filePath 文件相对路径
     * @param bucketName 桶名（可选）
     * @param expires 链接过期时间（秒），默认3600秒（1小时）
     * @return 预览链接
     */
    @RateLimit(limitType = RateLimitType.IP, rate = 30, rateInterval = 1, timeUnit = TimeUnit.MINUTES, message = "操作过于频繁，请稍后再试")
    @GetMapping("/preview-url")
    public ResponseEntity<Map<String, Object>> getPreviewUrl(@RequestParam("filePath") String filePath,
                                                            @RequestParam(value = "bucketName", required = false) String bucketName,
                                                            @RequestParam(value = "expires", defaultValue = "3600") Integer expires) {

        log.info("获取文件预览链接 - filePath: {}, bucketName: {}, expires: {}", filePath, bucketName, expires);

        Map<String, Object> result = new HashMap<>();

        try {
            // 使用默认桶名如果未指定
            String targetBucket = StrUtil.isBlank(bucketName) ? minioUtils.getDefaultBucketName() : bucketName;

            // 检查文件是否存在
            if (!minioUtils.isObjectExist(targetBucket, filePath)) {
                result.put("success", false);
                result.put("message", "文件不存在");
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(result);
            }

            // 获取预览链接
            String previewUrl = minioUtils.getPresignedObjectUrl(targetBucket, filePath, expires);

            result.put("success", true);
            result.put("previewUrl", previewUrl);
            result.put("fileName", getFileNameFromPath(filePath));
            result.put("expires", expires);

            log.info("获取预览链接成功 - url: {}", previewUrl);
            return ResponseEntity.ok(result);

        } catch (Exception e) {
            log.error("获取预览链接失败 - filePath: {}, error: {}", filePath, e.getMessage(), e);
            result.put("success", false);
            result.put("message", "获取预览链接失败: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }



    /**
     * 获取文件信息
     * @param filePath 文件相对路径
     * @param bucketName 桶名（可选）
     * @return 文件信息
     */
    @RateLimit(key = "download.file.info", limitType = RateLimitType.IP, rate = 60, rateInterval = 1, timeUnit = TimeUnit.MINUTES, message = "操作过于频繁，请稍后再试")
    @GetMapping("/file/info")
    public ResponseEntity<Map<String, Object>> getFileInfo(@RequestParam("filePath") String filePath,
                                                          @RequestParam(value = "bucketName", required = false) String bucketName) {

        log.info("获取文件信息 - filePath: {}, bucketName: {}", filePath, bucketName);

        Map<String, Object> result = new HashMap<>();

        try {
            // 使用默认桶名如果未指定
            String targetBucket = StrUtil.isBlank(bucketName) ? minioUtils.getDefaultBucketName() : bucketName;

            // 检查文件是否存在
            if (!minioUtils.isObjectExist(targetBucket, filePath)) {
                result.put("success", false);
                result.put("message", "文件不存在");
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(result);
            }

            // 获取文件信息
            String fileInfo = minioUtils.getFileStatusInfo(targetBucket, filePath);
            String fileName = getFileNameFromPath(filePath);
            String fileExtension = getFileExtension(fileName);
            String contentType = getContentType(fileExtension);

            result.put("success", true);
            result.put("fileName", fileName);
            result.put("filePath", filePath);
            result.put("fileExtension", fileExtension);
            result.put("contentType", contentType);
            result.put("fileInfo", fileInfo);

            log.info("获取文件信息成功 - fileName: {}", fileName);
            return ResponseEntity.ok(result);

        } catch (Exception e) {
            log.error("获取文件信息失败 - filePath: {}, error: {}", filePath, e.getMessage(), e);
            result.put("success", false);
            result.put("message", "获取文件信息失败: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
        }
    }

    /**
     * 从文件路径中提取文件名
     */
    private String getFileNameFromPath(String filePath) {
        if (StrUtil.isBlank(filePath)) {
            return "unknown";
        }
        int lastSlashIndex = filePath.lastIndexOf('/');
        return lastSlashIndex >= 0 ? filePath.substring(lastSlashIndex + 1) : filePath;
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String fileName) {
        if (StrUtil.isBlank(fileName)) {
            return "";
        }
        int lastDotIndex = fileName.lastIndexOf('.');
        return lastDotIndex >= 0 ? fileName.substring(lastDotIndex + 1).toLowerCase() : "";
    }

    /**
     * 根据文件扩展名获取Content-Type
     */
    private String getContentType(String fileExtension) {
        return switch (fileExtension.toLowerCase()) {
            // 文档类型
            case "pdf" -> "application/pdf";
            case "doc" -> "application/msword";
            case "docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
            case "xls" -> "application/vnd.ms-excel";
            case "xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            case "ppt" -> "application/vnd.ms-powerpoint";
            case "pptx" -> "application/vnd.openxmlformats-officedocument.presentationml.presentation";
            case "txt" -> "text/plain";
            case "md" -> "text/markdown";

            // 图片类型
            case "jpg", "jpeg" -> "image/jpeg";
            case "png" -> "image/png";
            case "gif" -> "image/gif";
            case "bmp" -> "image/bmp";
            case "svg" -> "image/svg+xml";
            case "webp" -> "image/webp";

            // 音频类型
            case "mp3" -> "audio/mpeg";
            case "wav" -> "audio/wav";
            case "ogg" -> "audio/ogg";
            case "m4a" -> "audio/mp4";

            // 视频类型
            case "mp4" -> "video/mp4";
            case "avi" -> "video/x-msvideo";
            case "mov" -> "video/quicktime";
            case "wmv" -> "video/x-ms-wmv";
            case "flv" -> "video/x-flv";

            // 压缩文件
            case "zip" -> "application/zip";
            case "rar" -> "application/x-rar-compressed";
            case "7z" -> "application/x-7z-compressed";
            case "tar" -> "application/x-tar";
            case "gz" -> "application/gzip";

            // 代码文件
            case "html", "htm" -> "text/html";
            case "css" -> "text/css";
            case "js" -> "application/javascript";
            case "json" -> "application/json";
            case "xml" -> "application/xml";
            case "java" -> "text/x-java-source";
            case "py" -> "text/x-python";
            case "cpp", "c" -> "text/x-c";

            default -> "application/octet-stream";
        };
    }

    /**
     * 设置下载响应头
     */
    private void setDownloadHeaders(HttpServletResponse response, String fileName, HttpServletRequest request) {
        try {
            String fileExtension = getFileExtension(fileName);
            String contentType = getContentType(fileExtension);

            response.setContentType(contentType);

            // 根据User-Agent设置文件名编码
            String userAgent = request.getHeader("User-Agent");
            String encodedFileName;

            if (userAgent != null && userAgent.contains("MSIE")) {
                // IE浏览器
                encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
            } else {
                // 其他浏览器
                encodedFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }

            // 根据文件类型决定是否内联显示
            boolean isInline = isInlineDisplayType(fileExtension);
            String disposition = isInline ? "inline" : "attachment";

            response.setHeader("Content-Disposition", disposition + "; filename=\"" + encodedFileName + "\"");
            response.setHeader("Cache-Control", "no-cache");
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);

        } catch (Exception e) {
            log.error("设置下载响应头失败", e);
        }
    }

    /**
     * 判断文件类型是否适合内联显示
     */
    private boolean isInlineDisplayType(String fileExtension) {
        return switch (fileExtension.toLowerCase()) {
            case "pdf", "txt", "md", "html", "htm", "css", "js", "json", "xml" -> true;
            case "jpg", "jpeg", "png", "gif", "bmp", "svg", "webp" -> true;
            case "mp3", "wav", "ogg", "m4a" -> true;
            case "mp4", "avi", "mov", "wmv" -> true;
            default -> false;
        };
    }

    /**
     * 从文件信息中解析文件大小（简化实现）
     */
    private long getFileSizeFromInfo(String fileInfo) {
        // 这里是简化实现，实际应该解析StatObjectResponse的内容
        // 可以通过正则表达式或JSON解析来获取文件大小
        try {
            // 假设文件信息包含size字段，这里返回默认值
            return 1024 * 1024; // 1MB 默认值
        } catch (Exception e) {
            log.warn("解析文件大小失败: {}", fileInfo);
            return 1024 * 1024; // 1MB 默认值
        }
    }

    /**
     * 检查是否是客户端连接中断异常
     */
    private boolean isClientAbortException(Throwable throwable) {
        if (throwable == null) {
            return false;
        }

        // 检查异常类型和消息
        String className = throwable.getClass().getSimpleName();
        String message = throwable.getMessage();

        // 检查是否是客户端中断相关的异常
        if ("ClientAbortException".equals(className) ||
            "AsyncRequestNotUsableException".equals(className) ||
            (message != null && (
                message.contains("你的主机中的软件中止了一个已建立的连接") ||
                message.contains("Connection reset by peer") ||
                message.contains("Broken pipe") ||
                message.contains("Connection aborted") ||
                message.contains("ServletOutputStream failed to write")
            ))) {
            return true;
        }

        // 递归检查异常链
        Throwable cause = throwable.getCause();
        if (cause != null && cause != throwable) {
            return isClientAbortException(cause);
        }

        return false;
    }
}
